using System;
using System.Collections.Generic;
using System.Text;
using DirectShowLib;
using System.IO;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
using DirectShowLib.Utils;

namespace Lizk.DShowPlayer
{

    public enum PlayerState
    {
        NotLoaded,
        Loaded,
        Playing,
        Paused,
        Disposed,
        Menu
    }

    public class FilePlayer : Player, IDisposable
    {
        protected string filePath;
        private Guid subtitleSelector, audioSelector;

        public FilePlayer(string filePath)
        {
            this.filePath = filePath;
            DirectShowLib.IFileSourceFilter fs;
            
        }

        ~FilePlayer()
        {
                Dispose();
        }

        private void CanLoadFile()
        {
            if (state != PlayerState.NotLoaded)
                throw new Exception("The media file is already loaded.");

            if (string.IsNullOrEmpty(filePath))
                throw new ArgumentNullException("filePath");

            if (!File.Exists(filePath))
                throw new FileNotFoundException("The media file was not found", filePath);
        }


        public override void Load(Form owner, IVMRSurfaceAllocator9 allocator)
        {
            CanLoadFile();
            mainForm = owner;
            if (mainForm == null)
                throw new ArgumentException("A form is needed to receive messages");


                graphBuilder = (IFilterGraph2)new FilterGraph();

                AddVmr9(allocator);

                BuildGraph();
                GetSeekCaps();
                GetTrackInfo();
                GetFrameStepInterface();

                GetProcAmp();
                ChangeState(PlayerState.Loaded);
        }

        


        public override void Load(Control targetControl)
        {
            CanLoadFile();
            this.targetControl = targetControl;
            mainForm = targetControl.TopLevelControl as Form;
            if (mainForm == null)
                throw new ArgumentException("The target control does not have a toplevel form");

            try
            {

                int hr = 0;
                graphBuilder = (IFilterGraph2)new FilterGraph();
                
                BuildGraph();
                GetSeekCaps();
                noVideo = AudioOnly();
                if (!noVideo)
                {
                    GetFrameStepInterface();
                    GetTrackInfo();

                    resizeEvent = new EventHandler(targetControl_Resize);
                    targetControl.Resize += resizeEvent;
                    
                    // Setup the video window
                    hr = this.videoWindow.put_Owner(targetControl.Handle);
                    DsError.ThrowExceptionForHR(hr);

                    hr = this.videoWindow.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipSiblings | WindowStyle.ClipChildren);
                    DsError.ThrowExceptionForHR(hr);

                    hr = this.videoWindow.SetWindowPosition(0, 0, targetControl.Width, targetControl.Height);
                    DsError.ThrowExceptionForHR(hr);
                }
                ChangeState(PlayerState.Loaded);
            }
            catch
            {
                Dispose();
                throw;
            }

        }

        private void targetControl_Resize(object sender, EventArgs e)
        {
            if (videoWindow != null)
            {
                int hr = this.videoWindow.SetWindowPosition(0, 0, targetControl.Width, targetControl.Height);
                DsError.ThrowExceptionForHR(hr);
            }
        }


        public override void Play()
        {
            if (mediaControl == null)
                throw new Exception("No file is loaded");
            int hr;
            if (state == PlayerState.Loaded)
            {
                FilterState fs;
                hr = mediaControl.Pause();
                DsError.ThrowExceptionForHR(hr);
                mediaControl.GetState(1000, out fs);
            }

            hr = mediaControl.Run();
            DsError.ThrowExceptionForHR(hr);

            ChangeState(PlayerState.Playing);
        }

        protected override TimeSpan GetPosition()
        {
            long pos;
            int hr;
            hr = mediaSeeking.GetCurrentPosition(out pos);
            DsError.ThrowExceptionForHR(hr);
            return new TimeSpan(pos);
        }

        public void Seek(long ticks, bool absolute)
        {
            if (mediaControl == null)
                return;

            if ((seekCap & AMSeekingSeekingCapabilities.CanSeekAbsolute) == AMSeekingSeekingCapabilities.CanSeekAbsolute && duration.TotalMilliseconds > 0)
            {
                long pos;
                int hr;

                hr = mediaSeeking.GetCurrentPosition(out pos);
                DsError.ThrowExceptionForHR(hr);

                if (!absolute)
                {
                    ticks += pos;
                }

                if (ticks < 0)
                    ticks = 0;

                if (ticks >= duration.Ticks)
                {
                    ticks = duration.Ticks;
                    hr = mediaControl.Stop();
                    DsError.ThrowExceptionForHR(hr);
                    ChangeState(PlayerState.Loaded);
                }
                FilterState fs;

                hr = mediaControl.Pause();
                if(hr == 1) 
                {
                    hr = mediaControl.GetState(200, out fs);
                    DsError.ThrowExceptionForHR(hr);
                }

                hr = mediaSeeking.SetPositions(ticks, AMSeekingSeekingFlags.AbsolutePositioning, 0, AMSeekingSeekingFlags.NoPositioning);
                DsError.ThrowExceptionForHR(hr);


                hr = mediaControl.Pause();
                if (hr == 1)
                {
                    hr = mediaControl.GetState(200, out fs);
                    DsError.ThrowExceptionForHR(hr);
                }
            }
        }


        private void BuildGraph()
        {
            int hr;
            hr = graphBuilder.RenderFile(filePath, null);
            DsError.ThrowExceptionForHR(hr);

            // QueryInterface for DirectShow interfaces
            this.mediaControl = (IMediaControl)this.graphBuilder;
            this.mediaEventEx = (IMediaEventEx)this.graphBuilder;
            this.mediaSeeking = (IMediaSeeking)this.graphBuilder;
            this.mediaPosition = (IMediaPosition)this.graphBuilder;

            // Query for video interfaces, which may not be relevant for audio files
            this.videoWindow = this.graphBuilder as IVideoWindow;
            this.basicVideo = this.graphBuilder as IBasicVideo;

            // Query for audio interfaces, which may not be relevant for video-only files
            this.basicAudio = this.graphBuilder as IBasicAudio;
            
            if (mainForm.InvokeRequired)
            {
                mainForm.Invoke(new WaitCallback(delegate(object o)
                {
                    FilePlayer p = (FilePlayer)o;
                    // Have the graph signal event via window callbacks for performance
                    int r = p.mediaEventEx.SetNotifyWindow(p.mainForm.Handle, WMGraphNotify, new IntPtr(p.GetHashCode())); // send notifications but this fileplayer should only read notifications that it owns.
                    DsError.ThrowExceptionForHR(r);
                }), this);
            } else {
                // Have the graph signal event via window callbacks for performance
                hr = mediaEventEx.SetNotifyWindow(mainForm.Handle, WMGraphNotify, new IntPtr(this.GetHashCode())); // send notifications but this fileplayer should only read notifications that it owns.
                DsError.ThrowExceptionForHR(hr);
            }

#if DEBUG
            remoteGraph = new DsROTEntry(graphBuilder);
#endif

        }

        private bool GetFrameStepInterface()
        {
            int hr = 0;

            IVideoFrameStep frameStepTest = null;

            // Get the frame step interface, if supported
            frameStepTest = (IVideoFrameStep)this.graphBuilder;

            // Check if this decoder can step
            hr = frameStepTest.CanStep(0, null);
            if (hr == 0)
            {
                this.frameStep = frameStepTest;
                return true;
            }
            else
            {
                Marshal.ReleaseComObject(frameStepTest);
                return false;
            }
        }


        protected void GetTrackInfo()
        {
            int hr;
            SortedDictionary<int, string> grp2Subs = new SortedDictionary<int, string>(), vobsubSubs = new SortedDictionary<int, string>();
            Guid vobsubSelector = Guid.Empty, grp2Selector = Guid.Empty;
            int grp2CurrentSub = 0, vobsubCurrentSub = 0;

            if (graphBuilder == null)
                throw new ArgumentNullException("graphBuilder");

            Subtitles.Clear();
            AudioTracks.Clear();

            IEnumFilters enumFilters;
            hr = graphBuilder.EnumFilters(out enumFilters);
            if (hr == 0)
            {
                IBaseFilter[] filters = new IBaseFilter[1];

                while (enumFilters.Next(filters.Length, filters, IntPtr.Zero) == 0)
                {
                    FilterInfo filterInfo;

                    hr = filters[0].QueryFilterInfo(out filterInfo);
                    DsError.ThrowExceptionForHR(hr);
                    Guid cl;
                    filters[0].GetClassID(out cl);
                    
                    if (hr == 0)
                    {
                        if (filterInfo.pGraph != null)
                            Marshal.ReleaseComObject(filterInfo.pGraph);

                        
                        IAMStreamSelect iss = filters[0] as IAMStreamSelect;
                        
                        if (iss != null)
                        {
                            int count;
                            hr = iss.Count(out count);
                            DsError.ThrowExceptionForHR(hr);
                            for (int i = 0; i < count; i++)
                            {
                                AMMediaType type;
                                AMStreamSelectInfoFlags flags;
                                int plcid, pwdGrp; // language
                                String pzname;
                                Thread t = Thread.CurrentThread;
                                object ppobject, ppunk;
                                hr = iss.Info(i, out type, out flags, out plcid, out pwdGrp, out pzname, out ppobject, out ppunk);
                                DsError.ThrowExceptionForHR(hr);
                                if (ppobject != null)
                                    Marshal.ReleaseComObject(ppobject);

                                if (type != null)
                                {
                                    DsUtils.FreeAMMediaType(type);
                                }

                                if (ppunk != null)
                                    Marshal.ReleaseComObject(ppunk);

                                if (pwdGrp == 2)
                                {
                                    if (grp2Selector == Guid.Empty)
                                        filters[0].GetClassID(out grp2Selector);
                                    if ((AMStreamSelectInfoFlags.Enabled & flags) == AMStreamSelectInfoFlags.Enabled)
                                    {
                                        grp2CurrentSub = i;
                                    }
                                    grp2Subs.Add(i, pzname);
                                }

                                if (pwdGrp == 1)
                                {
                                    if (audioSelector == Guid.Empty)
                                        filters[0].GetClassID(out audioSelector);
                                    if ((AMStreamSelectInfoFlags.Enabled & flags) == AMStreamSelectInfoFlags.Enabled)
                                    {
                                            audioTrack = i;
                                    }
                                    AudioTracks.Add(i, pzname);
                                }


                                if (pwdGrp == 6590033)
                                {
                                    if (vobsubSelector == Guid.Empty)
                                        filters[0].GetClassID(out vobsubSelector);
                                    if ((AMStreamSelectInfoFlags.Enabled & flags) == AMStreamSelectInfoFlags.Enabled)
                                    {
                                        vobsubCurrentSub = i;
                                    }
                                    vobsubSubs.Add(i, pzname);
                                }
                            }
                        }
                    }

                    Marshal.ReleaseComObject(filters[0]);
                }
                Marshal.ReleaseComObject(enumFilters);
            }
            if (grp2Subs.Count > 0)
            {
                foreach (KeyValuePair<int, string> sub in grp2Subs)
                {
                    Subtitles.Add(sub.Key, sub.Value);
                }
                    subtitle = grp2CurrentSub;
                subtitleSelector = grp2Selector;
            }
            else if (vobsubSubs.Count > 0)
            {
                foreach (KeyValuePair<int, string> sub in vobsubSubs)
                {
                    Subtitles.Add(sub.Key, sub.Value);
                }
                subtitle = vobsubCurrentSub;
                subtitleSelector = vobsubSelector;
            }
            UpdateTrackAmount();
        }



        private bool AudioOnly()
        {
            int hr = 0;
            OABool lVisible;

            if ((this.videoWindow == null) || (this.basicVideo == null))
            {
                // Audio-only files have no video interfaces.  This might also
                // be a file whose video component uses an unknown video codec.

                return true;
            }

            hr = this.videoWindow.get_Visible(out lVisible);
            if (hr < 0)
            {
                // If this is an audio-only clip, get_Visible() won't work.
                //
                // Also, if this video is encoded with an unsupported codec,
                // we won't see any video, although the audio will work if it is
                // of a supported format.
                if (hr == unchecked((int)0x80004002)) //E_NOINTERFACE
                {
                    return true;
                }
                else
                    DsError.ThrowExceptionForHR(hr);
            }
            return false;
        }

        public override void Dispose()
        {
            int hr;

#if DEBUG
            if (remoteGraph != null)
            {
                remoteGraph.Dispose();
                remoteGraph = null;
            }
#endif

            


            if (mediaControl != null)
            {
                hr = mediaControl.StopWhenReady();
            }

            mainForm = null;
            if (mediaEventEx != null)
            {
                hr = mediaEventEx.SetNotifyWindow(IntPtr.Zero, 0, IntPtr.Zero);
            }


            // Relinquish ownership (IMPORTANT!) after hiding video window
            if (!noVideo && this.videoWindow != null && targetControl != null)
            {
                hr = this.videoWindow.put_Visible(OABool.False);
                DsError.ThrowExceptionForHR(hr);
                hr = this.videoWindow.put_Owner(IntPtr.Zero);
                DsError.ThrowExceptionForHR(hr);
            }

            if (targetControl != null && resizeEvent != null)
            {
                targetControl.Resize -= resizeEvent;
            }

            if (vmr9 != null)
            {
                Marshal.ReleaseComObject(vmr9);
                vmr9 = null;
            }
            

            mediaControl = null;
            mediaSeeking = null;
            mediaPosition = null;
            mediaEventEx = null;
            videoWindow = null;
            basicVideo = null;
            basicAudio = null;
            frameStep = null;


            if (graphBuilder != null)
            {
                Debug.WriteLine(this.filePath + " was disposed");
                Marshal.ReleaseComObject(graphBuilder);
                graphBuilder = null;
                ChangeState(PlayerState.Disposed);
            }
        }


        private void GetSeekCaps()
        {
            int hr;
            
            if (mediaSeeking == null)
                return;

            // Get Seeking Caps
            hr = mediaSeeking.GetCapabilities(out seekCap);
            DsError.ThrowExceptionForHR(hr);
            // Get Duration
            long videoDuration = 0;
            if ((seekCap & AMSeekingSeekingCapabilities.CanGetDuration) == AMSeekingSeekingCapabilities.CanGetDuration)
            {
                hr = mediaSeeking.GetDuration(out videoDuration);
                DsError.ThrowExceptionForHR(hr);
            }
            duration = new TimeSpan(videoDuration);
        }




        public void Pause()
        {
            if (mediaControl == null)
                return;
            FilterState state;
            int hr;
            hr = mediaControl.GetState(1000, out state);
            DsError.ThrowExceptionForHR(hr);
            switch (state)
            {
                case FilterState.Running:
                    hr = mediaControl.Pause();
                    if (hr == 1)
                    {
                        hr = mediaControl.GetState(5000, out state);
                        DsError.ThrowExceptionForHR(hr);
                    }
                    break;
            }
            DsError.ThrowExceptionForHR(hr);
            ChangeState(PlayerState.Paused);
        }


        public override void EnableAudioTrack(int id)
        {
            int hr;

            IEnumFilters enumFilters;
            hr = graphBuilder.EnumFilters(out enumFilters);
            if (hr == 0)
            {
                IBaseFilter[] filters = new IBaseFilter[1];

                while (enumFilters.Next(filters.Length, filters, IntPtr.Zero) == 0)
                {
                    FilterInfo filterInfo;

                    hr = filters[0].QueryFilterInfo(out filterInfo);
                    DsError.ThrowExceptionForHR(hr);

                    if (hr == 0)
                    {
                        if (filterInfo.pGraph != null)
                            Marshal.ReleaseComObject(filterInfo.pGraph);

                        Guid clid;
                        filters[0].GetClassID(out clid);
                        if (clid != audioSelector)
                            continue;

                        IAMStreamSelect iss = filters[0] as IAMStreamSelect;

                        if (iss != null)
                        {
                            iss.Enable(id, AMStreamSelectEnableFlags.Enable);
                            audioTrack = id;
                        }
                    }
                    Marshal.ReleaseComObject(filters[0]);
                }
                Marshal.ReleaseComObject(enumFilters);
            }
            
        }

        public override void EnableSubtitle(int id)
        {

            IBaseFilter filter = FilterGraphTools.FindFilterByClsid(graphBuilder, subtitleSelector);
            if (filter != null)
            {
                IAMStreamSelect iss = filter as IAMStreamSelect;

                if (iss != null)
                {
                    iss.Enable(id, AMStreamSelectEnableFlags.Enable);
                    subtitle = id;
                }
                Marshal.ReleaseComObject(filter);
            }
            /*
            IEnumFilters enumFilters;
            hr = graphBuilder.EnumFilters(out enumFilters);
            if (hr == 0)
            {
                IBaseFilter[] filters = new IBaseFilter[1];
                int fetched;

                while (enumFilters.Next(filters.Length, filters, out fetched) == 0)
                {
                    FilterInfo filterInfo;

                    hr = filters[0].QueryFilterInfo(out filterInfo);
                    DsError.ThrowExceptionForHR(hr);

                    if (hr == 0)
                    {
                        if (filterInfo.pGraph != null)
                            Marshal.ReleaseComObject(filterInfo.pGraph);

                        Guid clid;
                        filters[0].GetClassID(out clid);
                        if (clid != subtitleSelector)
                            continue;

                        IAMStreamSelect iss = filters[0] as IAMStreamSelect;

                        if (iss != null)
                        {
                            iss.Enable(id, AMStreamSelectEnableFlags.Enable);
                            subtitle = id;
                        }
                    }

                    Marshal.ReleaseComObject(filters[0]);
                }
                Marshal.ReleaseComObject(enumFilters);
            }*/
        }

        protected override bool Message(ref Message m)
        {
            switch (m.Msg)
            {
                case WMGraphNotify:
                    {
                        if (mediaEventEx == null)
                            return false;
                        IntPtr p1, p2; int hr = 0;
                        EventCode code;
                        do
                        {
                            hr = mediaEventEx.GetEvent(out code, out p1, out p2, 0);
                            if (hr < 0)
                            {
                                break;
                            }

                            switch (code)
                            {
                                case EventCode.LengthChanged:
                                    GetSeekCaps();
                                    break;
                                case EventCode.Complete:
                                    Pause();
                                    break;
                            }

                            hr = mediaEventEx.FreeEventParams(code, p1, p2);
                        }
                        while (hr == 0);
                        break;
                    }
            }
            return false;
        }
    }

}
